package appTool

import (
	Ctx "context"
	"errors"
	"fmt"
	"gitee.com/kinwyb/appTools/common/socket"
	"gitee.com/kinwyb/appTools/log"
	"gitee.com/kinwyb/conv"
	"github.com/spf13/cobra"
	"io/fs"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

// 热重启实现

// GracefulReload 应用热重启
func (a *Application) GracefulReload() {
	a.doGracefulReload = true
	log.AppTool.Info("等待应用热重启...")
	time.Sleep(3 * time.Second)
	log.AppTool.Info("应用热重启开始")
	var httpListenFile *os.File
	var rpcListenFile *os.File
	if a.http != nil {
		httpListenFile = a.http.listenFile
	}
	if a.rpcx != nil {
		rpcListenFile = a.rpcx.listenFile
	}
	args := os.Args[1:]
	hasReload := false
	for _, v := range args {
		if v == "--graceful" {
			hasReload = true
			break
		}
	}
	if !hasReload {
		args = append(args, "--graceful")
	}
	cmd := exec.Command(os.Args[0], args...)
	cmd.ExtraFiles, cmd.Stdout, cmd.Stderr = []*os.File{httpListenFile, rpcListenFile}, os.Stdout, os.Stderr
	err := cmd.Start()
	if err != nil {
		log.AppTool.WithError(err).Error("cmd start fail")
		return
	}
	log.AppTool.Info("forked new pid : ", cmd.Process.Pid)
	log.AppTool.Info("应用热重启结束")
	time.Sleep(3 * time.Second) //延时3秒确保unix socket已经正常启动
	ctx := Ctx.Background()
	client := newUnixClient(fmt.Sprintf("%s.%d", a.AppName(), cmd.Process.Pid))
	err = client.start(ctx)
	if err == nil {
		for {
			msg := client.sendCommand("status")
			log.AppTool.Info("新应用状态:" + msg)
			if msg == "系统启动完成" {
				break
			}
			time.Sleep(time.Second)
		}
	}
	client.close()
	a.Close()
	unixFile := fmt.Sprintf("%s.%d", a.AppName(), os.Getpid())
	_ = os.Remove(unixFile) //移除unix套接字
}

func (a *Application) startUnixSocket(ctx Ctx.Context) {
	config := &socket.TcpServerConfig{
		Log: log.AppTool,
		ErrorHandler: func(errType socket.ErrorType, err error, clientID ...string) {
			log.AppTool.Errorf("%s => %s", errType.String(), err.Error())
		},
		NewClientHandler: func(clientID string) socket.TcpProtocol {
			log.AppTool.Infof("新客户端:%s", clientID)
			return nil
		},
		CloseHandler: func() {
			log.AppTool.Info("服务端关闭")
		},
		ClientCloseHandler: func(clientID string) {
			log.AppTool.Infof("客户端[%s]连接关闭", clientID)
		},
	}
	server, err := socket.NewTcpServer(ctx, config)
	config.MessageHandler = func(clientID string, msg []byte) {
		msgStr := string(msg)
		switch msgStr {
		case "version":
			server.Write(clientID, []byte(Version))
		case "reload":
			if a.doGracefulReload {
				server.Write(clientID, []byte("正在重启中"))
			} else if a.statusMsg != "系统启动完成" { //如果系统没有启动完成不允许重启
				server.Write(clientID, []byte("系统启动中无法重启"))
			} else {
				a.doGracefulReload = true
				go a.GracefulReload()
				server.Write(clientID, []byte("ok"))
			}
		case "shutdown":
			server.Write(clientID, []byte("ok"))
			time.Sleep(1 * time.Second)
			server.Close()
			a.Close()
		case "status":
			server.Write(clientID, []byte(a.statusMsg))
		default:
			server.Write(clientID, []byte("未知命令"))
		}
	}
	if err != nil {
		log.AppTool.WithError(err).Error("unix socket 初始化失败")
		return
	}
	pid := os.Getpid()
	unixFilePath := fmt.Sprintf("%s.%d", a.AppName(), pid)
	_ = filepath.Walk("./", func(path string, info fs.FileInfo, err error) error {
		if err != nil {
			return err
		}
		fileName := info.Name()
		if fileName == unixFilePath || info.IsDir() {
			return nil
		}
		if strings.HasPrefix(fileName, a.AppName()+".") {
			p := strings.Split(fileName, ".")
			if len(p) != 2 || conv.ToInt64Default(p[1], -1) < 1 {
				return nil
			}
			log.AppTool.Info("删除无效链接文件:" + path)
			_ = os.Remove(path)
		}
		return nil
	})
	addr, err := net.ResolveUnixAddr("unix", unixFilePath)
	if err != nil {
		log.AppTool.WithError(err).Error("unix socket 初始化失败")
		return
	}
	listen, err := net.ListenUnix("unix", addr)
	if err != nil {
		log.AppTool.WithError(err).Error("unix socket 初始化失败")
		return
	}
	a.unixSocket = listen
	server.Start(listen)
}

type appGracefulReload struct {
	cmd      *cobra.Command
	app      *Application
	client   *unixClient
	unixFile *string
}

func newAppGracefulReload() *appGracefulReload {
	ret := &appGracefulReload{}
	ret.cmd = &cobra.Command{
		Use:   "single {version | reload | shutdown | status}",
		Short: "信号",
		Long:  `往主进程发送信号获取相关信息[version,reload,shutdown,status]`,
		Run:   ret.cmdRun,
	}
	ret.unixFile = ret.cmd.Flags().StringP("unixFile", "f", "", "套接字文件")
	return ret
}

func (a *appGracefulReload) cmdRun(cmd *cobra.Command, args []string) {
	if len(args) < 1 {
		log.AppTool.Error("空命令")
		return
	}
	command := args[0]
	switch command {
	case "version", "reload", "shutdown", "status":
	default:
		log.AppTool.Error("未知命令:" + command)
		return
	}
	var unixFiles []string
	if *a.unixFile != "" {
		unixFiles = []string{*a.unixFile}
	} else {
		pid := os.Getpid()
		_ = filepath.Walk("./", func(path string, info fs.FileInfo, err error) error {
			if info.IsDir() && info.Name() != "." {
				return filepath.SkipDir
			}
			if strings.HasPrefix(info.Name(), a.app.AppName()+".") && info.Name() != fmt.Sprintf("%s.%d", a.app.AppName(), pid) {
				pidVal := strings.TrimPrefix(info.Name(), a.app.AppName()+".")
				p := conv.ToInt64(pidVal)
				if p > 0 {
					unixFiles = append(unixFiles, "./"+info.Name())
				}
			}
			return nil
		})
	}
	if len(unixFiles) != 1 {
		log.AppTool.Error("未找到唯一的unix socket file")
		return
	}
	ctx := Ctx.Background()
	a.client = newUnixClient(unixFiles[0])
	a.client.start(ctx)
	msg := a.client.sendCommand(command)
	log.AppTool.Info("收到结果:" + msg)
	a.client.close()
}

type unixClient struct {
	unixFile   string
	reconveMsg chan string
	client     *socket.TcpClient
}

func newUnixClient(unixFile string) *unixClient {
	return &unixClient{
		unixFile:   unixFile,
		reconveMsg: make(chan string, 1),
	}
}

func (u *unixClient) start(ctx Ctx.Context) error {
	client, err := socket.NewTcpClient(ctx, &socket.TcpClientConfig{
		AutoReConnect:     true,
		Log:               log.AppTool,
		ReConnectWaitTime: 5 * time.Second,
		ErrorHandler: func(errorType socket.ErrorType, e error) {
			log.AppTool.Errorf("%s => %s", errorType, e.Error())
		},
		MessageHandler: func(msg []byte) {
			u.reconveMsg <- string(msg)
		},
		CloseHandler: func() {
			log.AppTool.Info("连接关闭")
		},
		ConnectTimeOut: 30 * time.Second,
	})
	if err != nil {
		log.AppTool.WithError(err).Error("unix socket client 初始化失败")
		return err
	}
	addr, err := net.ResolveUnixAddr("unix", u.unixFile)
	if err != nil {
		log.AppTool.WithError(err).Error("unix socket 初始化失败")
		return err
	}
	conn, err := net.DialUnix("unix", nil, addr)
	if err != nil {
		log.AppTool.WithError(err).Error("unix socket 连接失败")
		return err
	}
	connect := client.ConnectWithConn(conn)
	log.AppTool.Infof("服务器连接状态: %v", connect)
	if !connect {
		log.AppTool.Error("服务器连接失败")
		return errors.New("服务器连接失败")
	}
	u.client = client
	return nil
}

// 发送命令
func (u *unixClient) sendCommand(cmd string) string {
	u.client.Write([]byte(cmd))
	return <-u.reconveMsg
}

func (u *unixClient) close() {
	if u.client != nil {
		u.client.Close()
	}
	close(u.reconveMsg)
}
